package funs

import (
	"fmt"
	"html/template"
	"path/filepath"
	"sort"
	"strings"

	underscore "github.com/ahl5esoft/golang-underscore"
	"github.com/gobwas/glob"
	"xorm.io/xorm/names"
	"xorm.io/xorm/schemas"
)

func GetMapperByName(mapname string) names.Mapper {
	switch mapname {
	case "gonic":
		return names.LintGonicMapper
	case "same":
		return names.SameMapper{}
	default:
		return names.SnakeMapper{}
	}
}
func FilterTables(tables []*schemas.Table, excludeTables []string) []*schemas.Table {
	var res = make([]*schemas.Table, 0, len(tables))
	underscore.Chain(tables).
		Filter(func(tbl schemas.Table, _ int) bool {
			for _, exclude := range excludeTables {
				s, _ := glob.Compile(exclude)
				if s.Match(tbl.Name) {
					return false
				}
			}

			return true
		}).
		Each(func(tbl schemas.Table, _ int) {
			res = append(res, &tbl)
		})

	return res
}

type Map[T any, TMap map[string]T] struct {
	kv      map[string]string
	isUpper bool
	old     map[string]string
}

func NewMap[T any, TMap map[string]T](cps map[string]T, isUpper bool, f func(T) string) Map[T, TMap] {
	cm := Map[T, TMap]{
		kv:      make(map[string]string),
		old:     make(map[string]string),
		isUpper: isUpper,
	}
	for key, val := range cps {
		vstr := f(val)
		if isUpper {
			key := strings.ToUpper(key)
			cm.kv[key] = vstr
			cm.old[key] = vstr
		} else {
			cm.kv[key] = vstr
			cm.old[key] = vstr
		}
	}
	return cm
}

func (m *Map[T, TMap]) ResetAdd(cps map[string]T, f func(T) string) {
	m.kv = make(map[string]string)
	for key, val := range m.old {
		m.kv[key] = val
	}
	for key, val := range cps {
		vstr := f(val)
		if m.isUpper {
			m.kv[strings.ToUpper(key)] = vstr
		} else {
			m.kv[key] = vstr
		}
	}
}

func (m Map[T, TMap]) Map() map[string]string {
	return m.kv
}

func (m Map[T, TMap]) Get(key string) (string, bool) {
	val, is := m.kv[key]
	return val, is
}

func Import[T any, TMap map[string]T](tyMap Map[T, TMap], table *schemas.Table, colNameTypeMap map[string]string, importMap map[string]string) (res []ImporatPath) {
	cols := table.Columns()
	isMap := make(map[string]struct{})
	for _, col := range cols {
		colNameType, is := colNameTypeMap[col.Name]
		if is && len(colNameType) != 0 {
			vks := strings.Split(colNameType, ".")
			if len(vks) > 1 {
				tyName := vks[0]
				path, is := importMap[tyName]
				if !is {
					panic(tyName + " not found import")
				}
				basePath := filepath.Base(path)
				_, is = isMap[basePath]
				if is {
					continue
				}
				isMap[basePath] = struct{}{}
				if len(basePath) != 0 && basePath != tyName {
					res = append(res, ImporatPath{
						Name: tyName,
						Path: path,
					})
					continue
				} else {
					res = append(res, ImporatPath{
						Name: "",
						Path: path,
					})
					continue
				}
			}
		}
		if v, ok := tyMap.Get(col.SQLType.Name); ok {
			if len(v) == 0 {
				continue
			}
			vks := strings.Split(v, ".")
			if len(vks) > 1 {
				tyName := vks[0]
				path, is := importMap[tyName]
				if !is {
					panic(tyName + " not found import")
				}
				basePath := filepath.Base(path)
				_, is = isMap[basePath]
				if is {
					continue
				}
				isMap[basePath] = struct{}{}
				if len(basePath) != 0 && basePath != tyName {
					res = append(res, ImporatPath{
						Name: tyName,
						Path: path,
					})
					continue
				} else {
					res = append(res, ImporatPath{
						Name: "",
						Path: path,
					})
					continue
				}
			}
		}
		s := schemas.SQLType2Type(col.SQLType).String()
		_, is = isMap[s]
		if s == "time.Time" && !is {
			isMap[s] = struct{}{}
			res = append(res, ImporatPath{
				Name: "",
				Path: "timea",
			})
		}
	}
	return
}

func Type[T any, TMap map[string]T](tyMap Map[T, TMap], colNameTypeMap map[string]string) func(c *schemas.Column) string {
	return func(c *schemas.Column) string {
		colNameType, is := colNameTypeMap[c.Name]
		if is && len(colNameType) != 0 {
			return colNameType
		}
		if v, ok := tyMap.Get(c.SQLType.Name); ok {
			if len(v) != 0 {
				return v
			}
		}
		s := schemas.SQLType2Type(c.SQLType).String()
		if s == "[]uint8" {
			return "[]byte"
		}
		return s
	}
}

func Json[T any, TMap map[string]T](tyMap Map[T, TMap], mapper names.Mapper) func(table *schemas.Table, col *schemas.Column) template.HTML {
	return func(table *schemas.Table, col *schemas.Column) template.HTML {
		if v, ok := tyMap.Get(col.Name); ok {
			if v == "-" {
				return template.HTML(`json:"-"`)
			}
			if len(v) != 0 {
				return template.HTML(fmt.Sprintf(`json:"%v"`, v))
			}
		}
		name := mapper.Table2Obj(col.Name)
		if len(name) > 0 {
			name = strings.ToLower(name[0:1]) + name[1:]
		}
		return template.HTML(fmt.Sprintf(`json:"%v"`, name))
	}
}
func Tag(table *schemas.Table, col *schemas.Column) template.HTML {
	isNameId := col.FieldName == "Id"
	isIdPk := isNameId && schemas.SQLType2Type(col.SQLType).String() == "int64"

	var res []string
	if !col.Nullable {
		if !isIdPk {
			res = append(res, "not null")
		}
	}
	if col.IsPrimaryKey {
		res = append(res, "pk")
	}
	if col.Default != "" {
		res = append(res, "default "+col.Default)
	}
	if col.IsAutoIncrement {
		res = append(res, "autoincr")
	}

	if GlobalFixedFieldOld.CreatedAt == col.Name {
		res = append(res, "created")
	}

	if GlobalFixedFieldOld.UpdatedAt == col.Name {
		res = append(res, "updated")
	}

	if GlobalFixedFieldOld.DeletedAt == col.Name {
		res = append(res, "deleted")
	}

	if col.Comment != "" {
		com := strings.NewReplacer("\n", " ", "\t", " ", "\"", "").Replace(col.Comment)
		res = append(res, fmt.Sprintf("comment('%s')", com))
	}

	names := make([]string, 0, len(col.Indexes))
	for name := range col.Indexes {
		names = append(names, name)
	}
	sort.Strings(names)

	for _, name := range names {
		index := table.Indexes[name]
		var uistr string
		if index.Type == schemas.UniqueType {
			uistr = "unique"
		} else if index.Type == schemas.IndexType {
			uistr = "index"
		}
		if len(index.Cols) > 1 {
			uistr += "(" + index.Name + ")"
		}
		res = append(res, uistr)
	}

	nstr := col.SQLType.Name
	if col.Length != 0 {
		if col.Length2 != 0 {
			nstr += fmt.Sprintf("(%v,%v)", col.Length, col.Length2)
		} else {
			nstr += fmt.Sprintf("(%v)", col.Length)
		}
	} else if len(col.EnumOptions) > 0 { //enum
		nstr += "("
		opts := ""

		enumOptions := make([]string, 0, len(col.EnumOptions))
		for enumOption := range col.EnumOptions {
			enumOptions = append(enumOptions, enumOption)
		}
		sort.Strings(enumOptions)

		for _, v := range enumOptions {
			opts += fmt.Sprintf(",'%v'", v)
		}
		nstr += strings.TrimLeft(opts, ",")
		nstr += ")"
	} else if len(col.SetOptions) > 0 { //enum
		nstr += "("
		opts := ""

		setOptions := make([]string, 0, len(col.SetOptions))
		for setOption := range col.SetOptions {
			setOptions = append(setOptions, setOption)
		}
		sort.Strings(setOptions)

		for _, v := range setOptions {
			opts += fmt.Sprintf(",'%v'", v)
		}
		nstr += strings.TrimLeft(opts, ",")
		nstr += ")"
	}
	res = append(res, nstr)
	if len(res) > 0 {
		// if g.target.ColumnName {
		return template.HTML(fmt.Sprintf(`xorm:"'%s' %s"`, col.Name, strings.Join(res, " ")))
		// }

		// return template.HTML(fmt.Sprintf(`xorm:"%s"`, strings.Join(res, " ")))
	}

	return "TAG"
}
